/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.jakubiak.utils.javabean;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;

/**
 * Reflect JavaBean Object:
 * http://en.wikipedia.org/wiki/JavaBeans
 * Probably thread safe but not tested in such environment
 * 
 * @author Antek
 */
public class BeanReflector {

    private Object bean;
    
    private Collection<BeanProperty> properties = null;
    

    public BeanReflector(Object javaBean) {
        this.bean = javaBean;
    }

    public Object getBean() {
        return bean;
    }
    

    /**
     * Reflecting class and loading properties
     */
    private void init() {
        properties = new LinkedList<BeanProperty>();
        for(Method method : getBean().getClass().getMethods()) {

            boolean hasGetterSignature = method.getParameterTypes().length == 0 && method.getReturnType() != null;
            String fieldName = null;
            if(method.getName().startsWith("get") && hasGetterSignature) {
                fieldName = lowerCaseFirst(method.getName().substring(3));
            } else if (method.getName().startsWith("is") && hasGetterSignature) {
                fieldName = lowerCaseFirst(method.getName().substring(2));
            }
            if (fieldName==null) {
                continue;
            }
            try {
                Method setterMethod = getBean().getClass().getMethod("set"+upperCaseFirst(fieldName), method.getReturnType());
                boolean hasSetterSignature = setterMethod.getParameterTypes().length == 1 && ( setterMethod.getReturnType() == null || setterMethod.getReturnType() == void.class );
                if(hasSetterSignature) {
                    Field field = findField(fieldName, getBean().getClass());
                    properties.add(new BeanProperty(field, method, setterMethod));
                }
            } catch(NoSuchMethodException e) {
                // OK, this is not error, but getter is not java bean accessor
                continue;
            } catch(NoSuchFieldException e) {
                // OK, this is not error, but getter is not java bean accessor
                continue;
            }
        }
    }
    
    
    /**
     * Get all properties from bean
     * @return
     */
    public Collection<BeanProperty> getProperties() {
        if(properties!=null) {
            return properties;
        }
        synchronized(this) {
            if (properties==null) {
                init();
            }
        }
        return properties;
    }

    /**
     * Search for given field name with this and parant classess
     * @param fieldName
     * @param bean
     * @return
     */
    private Field findField(String fieldName, Class clazz) throws NoSuchFieldException {
        if(clazz==null) {
            throw new NoSuchFieldException("No such field: " + fieldName + " in class: " + getBean().getClass().getName());
        }
        if(Object.class.equals(clazz)) {
            throw new NoSuchFieldException("No such field: " + fieldName + " in class: " + getBean().getClass().getName());
        }
        try {
            return clazz.getDeclaredField(fieldName);
        } catch(NoSuchFieldException e) {
            // OK, in this class we doesn't have field, check superclass
            return findField(fieldName, clazz.getSuperclass());
        } catch(SecurityException e) {
            // OK, in this class we doesn't have field, check superclass
            return findField(fieldName, clazz.getSuperclass());
        }
    }
    
    
    public static String lowerCaseFirst(String string) {
        return String.valueOf(string.charAt(0)).toLowerCase()+string.substring(1);
    }

    public static String upperCaseFirst(String string) {
        return String.valueOf(string.charAt(0)).toUpperCase()+string.substring(1);
    }

    
    public BeanProperty getProperty(String fieldName) throws NoSuchFieldException {
        for(BeanProperty property : getProperties()) {
            if(fieldName.equals(property.getName())) {
                return property;
            }
        }
        throw new NoSuchFieldException("No such property: " + fieldName + " in class: " + getBean().getClass().getName());
    }
            
    
    public boolean containsMethod(String methodName) {
        for(BeanProperty property : getProperties()) {
            if(property.getGetterMethod().getName().equals(methodName)) {
                return true;
            }
            if(property.getSetterMethod().getName().equals(methodName)) {
                return true;
            }
        }
        return false;
    }
    
}
